﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;

namespace TFSLBALib
{
	/// <summary>
	/// This class handles a Team System Process Template
	/// You can open locally stored Process Template or from a Zip File
	/// A set of methods gives you the possibility to interact with the Process Template's content
	/// Helper method let you repack the Process Template, upload it in Team System
	/// </summary>
	public class ProcessTemplate : IDisposable
	{
		#region Data Members

		/// <summary>
		/// Zip File representing the packed version of the Process Template
		/// </summary>
		FileInfo		ZipFile = null;

		/// <summary>
		/// Directory where the Process Template is stored
		/// </summary>
		DirectoryInfo	RootDir	= null;

		/// <summary>
		/// If true, the RootDir is temporary and contains the unpack version of the ZipFile
		/// If false, the RootDir is a Working Directory
		/// </summary>
		bool			RootDirIsTemp	= false;

		/// <summary>
		/// If true, the Zipfile is temporary and should be deleted upon object's release
		/// </summary>
		bool			ZipFileIsTemp	= false;

		#endregion

		#region Constructors

		/// <summary>
		/// Create a Process Template object from a directory containing the template's files
		/// </summary>
		/// <param name="processtemplaterootdir">Root Directory of the Process Template</param>
		public ProcessTemplate(DirectoryInfo processtemplaterootdir)
		{
			// Checkings
			if ((processtemplaterootdir==null) || (processtemplaterootdir.Exists==false))		throw new ArgumentException("Invalid Argument", "processtemplaterootdir");

			// Assign the directory
			RootDir = processtemplaterootdir;
			RootDirIsTemp = false;

		}

		/// <summary>
		/// Create a Process Template object from a Zip File
		/// </summary>
		/// <param name="processtemplatezipfile">The Zip File containing the packed directory of the Process Template</param>
		/// <remarks>This construction is used typically with the file returned by IProcessTemplates.GetTemplateData()</remarks>
		public ProcessTemplate(FileInfo processtemplatezipfile)
		{
			// Checkings
			if (processtemplatezipfile.Exists == false)		throw new ArgumentException("File doesn't exist", "processtemplatezipfile");

			// Load from the Zip file
			LoadFromZip(processtemplatezipfile);

			// Set the FileInfo
			ZipFile = processtemplatezipfile;
		}

		/// <summary>
		/// Create a Process Template object from a existing one in a given Team Foundation Server
		/// </summary>
		/// <param name="tfs">The TFS where the Process Template is stored</param>
		/// <param name="processtemplatename">The Name of the Process Template</param>
		/// <remarks>The name can be obtained from the TeamplateHeader.Name property.</remarks>
		public ProcessTemplate(TeamFoundationServer tfs, string processtemplatename)
		{
			// Checkings
			if (tfs == null)								throw new ArgumentException("Null TFS object", "tfs");

			// Get the Process Template interface
			IProcessTemplates ptp = (IProcessTemplates)tfs.GetService(typeof(IProcessTemplates));
			
			// Get the template Zipfile
			try
			{
				int tpi = ptp.GetTemplateIndex(processtemplatename);
				String zippathfilename = ptp.GetTemplateData(tpi);

				// Zip File is temporary
				ZipFileIsTemp = true;

				// Set the FileInfo
				ZipFile = new FileInfo(zippathfilename);

				// Load from zip
				LoadFromZip(ZipFile);
			}
			catch (Exception e)
			{
				throw new Exception(String.Format("Couldn't find the Process Template nammed: {0}", processtemplatename), e);	
			}
		}

		#endregion

		#region Helpers

		private void LoadFromZip(FileInfo processtemplatezipfile)
		{
			if (processtemplatezipfile.Exists == false)		throw  new ArgumentException("File doesn't exist", "processtemplatezipfile");

			try
			{
				// Construct a temporary directory
				RootDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
				RootDirIsTemp = true;

				// Unpack the Zip File in the temporary directory
				FastZip		fz = new FastZip();
				fz.ExtractZip(processtemplatezipfile.FullName, RootDir.FullName, "");
			}
			catch (Exception e)
			{
				throw new Exception("Couldn't unpack the Process Template", e);
			}

			// Look for the "ProcessTemplate.xml" file in the root
			FileInfo rootxml = new FileInfo(Path.Combine(RootDir.FullName, "ProcessTemplate.xml"));
			if (rootxml.Exists == false)					throw new ArgumentException("Given Archive is not a Process Template", "processtemplatezipfile");
		}

		private XmlDocument LoadXmlDoc(String relpath)
		{
			XmlDocument xml = new XmlDocument();
			xml.Load(Path.Combine(RootDir.FullName, relpath));
			return xml;
		}

		#endregion

		#region Process Template Properties

		public String Name
		{
			get
			{
				try
				{
					return ProcessTemplateDoc.DocumentElement.SelectSingleNode("metadata/name").InnerText;
				}
				catch (Exception e)
				{
				}

				return "";
			}

			set
			{
				try
				{
					XmlNode name = ProcessTemplateDoc.DocumentElement.SelectSingleNode("metadata/name");
					name.InnerText = value;
				}
				catch (Exception e)
				{
				}
			}
		}

		public String Description
		{
			get
			{
				try
				{
					return ProcessTemplateDoc.DocumentElement.SelectSingleNode("metadata/description").InnerText;
				}
				catch (Exception e)
				{
				}

				return "";
			}

			set
			{
				try
				{
					XmlNode desc = ProcessTemplateDoc.DocumentElement.SelectSingleNode("metadata/description");
					desc.InnerText = value;
				}
				catch (Exception e)
				{
				}
			}
		}

		#endregion

		#region Main XML Access

		private XmlDocument ProcessTemplateDoc
		{
			get
			{
				if (_ProcessTemplateDoc!=null) return _ProcessTemplateDoc;

				_ProcessTemplateDoc = LoadXmlDoc("ProcessTemplate.xml");
				return _ProcessTemplateDoc;
			}
		}
		private XmlDocument _ProcessTemplateDoc = null;
		private bool		_ProcessTemplateDocDirty = false;

		private XmlDocument WorkItemsDoc
		{
			get
			{
				if (_WorkItemsDoc!=null) return _WorkItemsDoc;

				_WorkItemsDoc = LoadXmlDoc("WorkItem Tracking\\WorkItems.xml");
				return _WorkItemsDoc;
			}
		}
		private XmlDocument _WorkItemsDoc = null;
		private bool		_WorkItemsDocDirty = false;

		#endregion

		/// <summary>
		/// Dispose implementation
		/// </summary>
		/// <remarks>Free resources, delete temporary directory content if required</remarks>
		public void Dispose()
		{
			// Delete the directory if it's temporary
			if (RootDirIsTemp)
			{
				RootDir.Delete(true);
			}

			// Delete the Zip if it's temporary too
			if (ZipFileIsTemp)
			{
				ZipFile.Delete();
			}
		}

		#region Work Item access

		public String[] GetWITList()
		{
			List<String> witlist = new List<string>();

			// Get the Work Item Type definitions
			XmlNodeList wilist = WorkItemsDoc.SelectNodes("tasks/task[@id=\"WITs\"]/taskXml/WORKITEMTYPES/WORKITEMTYPE");
			foreach (XmlNode node in wilist)
			{
				try
				{
					XmlElement xel = node as XmlElement;
					string filename = xel.GetAttribute("fileName");
					XmlDocument witdoc = LoadXmlDoc(filename);

					// Get the name of the Work Item
					xel = witdoc.DocumentElement.SelectSingleNode("WORKITEMTYPE") as XmlElement;
					
					witlist.Add(xel.GetAttribute("name"));
				}
				catch (Exception)
				{
				}
			}
			return witlist.ToArray();
		}

		public String[] GetWIQLList()
		{
			List<String> wiqllist = new List<string>();

			// Get the Work Item Type definitions
			XmlNodeList wilist = WorkItemsDoc.SelectNodes("tasks/task[@id=\"Queries\"]/taskXml/QUERIES/Query");
			foreach (XmlNode node in wilist)
			{
				try
				{
					XmlElement xel = node as XmlElement;
					wiqllist.Add(xel.GetAttribute("name"));
				}
				catch (Exception)
				{
				}
			}
			return wiqllist.ToArray();
		}

		public XmlDocument GetWITDefinition(String witname)
		{
			// Get the Work Item Type definitions
			XmlNodeList wilist = WorkItemsDoc.SelectNodes("tasks/task[@id=\"WITs\"]/taskXml/WORKITEMTYPES/WORKITEMTYPE");
			foreach (XmlNode node in wilist)
			{
				try
				{
					XmlElement xel = node as XmlElement;
					string filename = xel.GetAttribute("fileName");
					XmlDocument witdoc = LoadXmlDoc(filename);

					// Get the name of the Work Item
					xel = witdoc.DocumentElement.SelectSingleNode("WORKITEMTYPE") as XmlElement;

					if (witname == xel.GetAttribute("name"))
					{
						return witdoc;
					}
				}
				catch (Exception)
				{
				}
			}
			return null;
		}
		public XmlDocument GetWIQLDefinition(String wiqlname)
		{
			// Get the Work Item Type definitions
			XmlNodeList wilist = WorkItemsDoc.SelectNodes("tasks/task[@id=\"Queries\"]/taskXml/QUERIES/Query");
			foreach (XmlNode node in wilist)
			{
				try
				{
					XmlElement xel = node as XmlElement;
					if (wiqlname == xel.GetAttribute("name"))
					{
						string wiqlpfn = xel.GetAttribute("fileName");
						return LoadXmlDoc(wiqlpfn);
					}
				}
				catch (Exception)
				{
				}
			}
			return null;
		}

		public bool AddWorkItemType(XmlDocument witdoc, bool updateifexist)
		{
			// Get the name of the WIT
			XmlElement xel = witdoc.DocumentElement.SelectSingleNode("WORKITEMTYPE") as XmlElement;
			String witname = xel.GetAttribute("name");

			// Loop through the WIT list to find a possible existing one
			XmlNodeList wilist = WorkItemsDoc.SelectNodes("tasks/task[@id=\"WITs\"]/taskXml/WORKITEMTYPES/WORKITEMTYPE");
			foreach (XmlNode node in wilist)
			{
				try
				{
					xel = node as XmlElement;
					string filename = xel.GetAttribute("fileName");
					XmlDocument curwitdoc = LoadXmlDoc(filename);

					// Get the name of the Work Item
					xel = curwitdoc.DocumentElement.SelectSingleNode("WORKITEMTYPE") as XmlElement;

					// Do we found the one to update
					if (xel.GetAttribute("name") == witname)
					{
						// Returns an error if we are not allowed to update
						if (updateifexist == false)		return false;

						// Replace the file
						witdoc.Save(Path.Combine(RootDir.FullName, filename));
						
						return true;
					}
				}
				catch (Exception)
				{
				}
			}


			// It's a new work item, add it
			string witstrippedname = witname.Replace(" ", "");
			string newwitfilename = String.Format("{0}{1}.xml", @"WorkItem Tracking\TypeDefinitions\", witstrippedname);
			witdoc.Save(Path.Combine(RootDir.FullName, newwitfilename));

			// Add the reference in the WorkItemsDoc
			XmlNode wits = WorkItemsDoc.SelectSingleNode("tasks/task[@id=\"WITs\"]/taskXml/WORKITEMTYPES");
			XmlElement witel = WorkItemsDoc.CreateElement("WORKITEMTYPE");
			witel.SetAttribute("fileName", newwitfilename);
			wits.AppendChild(witel);

			// Make the WorkItems.xml dirty
			_WorkItemsDocDirty = true;

			return true;
		}

		public bool AddQuery(String wiqlname, XmlDocument wiqldoc, bool updateifexist)
		{
			// Loop through the WIQL list to find a possible existing one
			XmlNodeList wilist = WorkItemsDoc.SelectNodes("tasks/task[@id=\"Queries\"]/taskXml/QUERIES/Query");
			foreach (XmlNode node in wilist)
			{
				try
				{
					// Get the current Query Element
					XmlElement xel = node as XmlElement;

					// Do we found the one to update
					if (xel.GetAttribute("name") == wiqlname)
					{
						// Returns an error if we are not allowed to update
						if (updateifexist == false) return false;

						// Get the filename of the XML file storing the query
						String filename = xel.GetAttribute("fileName");

						// Replace the file
						wiqldoc.Save(Path.Combine(RootDir.FullName, filename));

						return true;
					}
				}
				catch (Exception)
				{
				}
			}

			// It's a new Query, add it
			string wiqlstrippedname = wiqlname.Replace(" ", "");
			string newwiqlfilename = String.Format("{0}{1}.wiq", @"WorkItem Tracking\Queries\", wiqlstrippedname);
			string fullpathname = Path.Combine(RootDir.FullName, newwiqlfilename);
			wiqldoc.Save(fullpathname);

			// Add the reference in the WorkItemsDoc
			XmlNode wiqls = WorkItemsDoc.SelectSingleNode("tasks/task[@id=\"Queries\"]/taskXml/QUERIES");
			XmlElement wiqlel = WorkItemsDoc.CreateElement("Query");
			wiqlel.SetAttribute("name", wiqlname);
			wiqlel.SetAttribute("fileName", newwiqlfilename);
			wiqls.AppendChild(wiqlel);

			// Make the WorkItems.xml dirty
			_WorkItemsDocDirty = true;

			return true;
		}

		#endregion

		public bool Upload(TeamFoundationServer tfs, bool updateexisting)
		{
			// Checkings
			if (tfs == null) throw new ArgumentException("Null TFS object", "tfs");

			// Check if the XML files should be saved
			if (_ProcessTemplateDocDirty == true)
			{
				String pfn = Path.Combine(RootDir.FullName, "ProcessTemplate.xml");
				ProcessTemplateDoc.Save(pfn);
				_ProcessTemplateDocDirty = false;
			}

			if (_WorkItemsDocDirty == true)
			{
				String pfn = Path.Combine(RootDir.FullName, "WorkItem Tracking\\WorkItems.xml");
				WorkItemsDoc.Save(pfn);
				_WorkItemsDocDirty = false;
			}

			// Pack the modified process template
			FastZip fz = new FastZip();
			String newptfile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
			fz.CreateZip(newptfile, RootDir.FullName, true, "");

			// Get the Process Template interface
			IProcessTemplates ptp = (IProcessTemplates)tfs.GetService(typeof(IProcessTemplates));
			TemplateHeader[] headers = ptp.TemplateHeaders();

			// Check if the template exist or not
			int tpi = ptp.GetTemplateIndex(Name);
			string metadata = ProcessTemplateDoc.DocumentElement.SelectSingleNode("metadata").OuterXml;
			
			// Create a new template
			if (tpi == -1)
			{
				tpi = ptp.AddTemplate(Name, Description, metadata, "visible");
			} else if (updateexisting == false)
			{
				return false;
			}

			// Upload the template
			ptp.AddUpdateTemplate(Name, Description, metadata, "visible", newptfile);

			// Delete the zip file
			File.Delete(newptfile);

			return true;
		}
	}
}
